﻿using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;

namespace zijian666.Data;

/// <summary>
/// 关于 <see cref="Type"/> 的扩展方法
/// </summary>
internal static class TypeExtensions
{
    /// <summary>
    /// 判断<paramref name="value"/>是否为 <code>null</code> 或<see cref="DBNull"/>
    /// </summary>
    public static bool IsNull([NotNullWhen(false)] this object? value) => value is null or DBNull;

    /// <summary>
    /// 类型转换
    /// </summary>
    public static object ChangeType(this object value, Type type)
    {
        if (value is DBValue db)
        {
            value = db.Value;
        }
        if (value.IsNull())
        {
            if (Nullable.GetUnderlyingType(type) is null)
            {
                return Convert.ChangeType(null, type);
            }
            return null;
        }
        if (type == typeof(object))
        {
            return value;
        }
        return Convert.ChangeType(value, Nullable.GetUnderlyingType(type) ?? type);
    }

    /// <summary>
    /// 判断<paramref name="value"/>是否为数据库可用的类型
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    internal static bool IsSimpleValue(this object value) =>
        // 基元类型为 Boolean、Byte、SByte、Int16、Int32、UInt16、UInt32、Int64、UInt64、IntPtr、UIntPtr、Char、 Double和 Single。
        value.GetType().IsPrimitive
        || value is DBValue
        || value is byte[] || value is string || value is Guid || value is DateTime
        || value is DateTimeOffset || value is TimeSpan || value is DBNull
        || value is Enum || value is decimal
        || value is JsonDocument || value is JsonElement
        || value is BitArray
#if net7
        || value is TimeOnly || value is DateOnly 
#endif
        ;

    // 判断是否为简单类型（基础类型、字符串、日期、Guid等）
    internal static bool IsSimpleType(this Type type)
    {
        Type underlyingType = Nullable.GetUnderlyingType(type) ?? type;
        return underlyingType.IsPrimitive
                || underlyingType == typeof(DBValue)
                || underlyingType == typeof(byte[])
                || underlyingType == typeof(string)
                || underlyingType == typeof(Guid)
                || underlyingType == typeof(DateTime)
                || underlyingType == typeof(DateTimeOffset)
                || underlyingType == typeof(TimeSpan)
                || underlyingType == typeof(DBNull)
                || underlyingType.IsEnum
                || underlyingType == typeof(decimal)
                || underlyingType == typeof(JsonDocument)
                || underlyingType == typeof(JsonElement)
                || underlyingType == typeof(BitArray)
#if net7
                || underlyingType == typeof(TimeOnly)
                || underlyingType == typeof(DateOnly)
#endif
                ;
    }

    /// <summary>
    /// 获取 <paramref name="value"/> 对应的数据库可用的类型值
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static object GetDbObject(this object? value)
    {
        if (value is DBValue db)
        {
            return GetDbObject(db.Value);
        }
        if (value.IsNull())
        {
            return DBNull.Value;
        }
        if (value.IsSimpleValue())
        {
            // 处理枚举
            if (value is Enum @enum)
            {
                return Convert.ChangeType(@enum, Enum.GetUnderlyingType(@enum.GetType()));
            }
            return value;
        }
        if (value is IConvertible convertible)
        {
            value = convertible.ToType(typeof(object), null);
        }
        return value;
    }

    internal static DbType? ToDbType(this string? typeName)
    {
        return typeName?.ToLower() switch
        {
            "bigint" => DbType.Int64,
            "binary" => DbType.Binary,
            "bit" => DbType.Boolean,
            "char" => DbType.StringFixedLength,
            "date" => DbType.Date,
            "datetime" => DbType.DateTime,
            "datetime2" => DbType.DateTime2,
            "datetimeoffset" => DbType.DateTimeOffset,
            "decimal" => DbType.Decimal,
            "float" => DbType.Double,
            "image" => DbType.Binary,
            "int" => DbType.Int32,
            "money" => DbType.Currency,
            "nchar" => DbType.StringFixedLength,
            "ntext" => DbType.String,
            "numeric" => DbType.Decimal,
            "nvarchar" => DbType.String,
            "real" => DbType.Single,
            "smalldatetime" => DbType.DateTime,
            "smallint" => DbType.Int16,
            "smallmoney" => DbType.Currency,
            "sql_variant" => DbType.Object,
            "text" => DbType.String,
            "time" => DbType.Time,
            "timestamp" => DbType.Binary,
            "tinyint" => DbType.Byte,
            "uniqueidentifier" => DbType.Guid,
            "varbinary" => DbType.Binary,
            "varchar" => DbType.String,
            "xml" => DbType.Xml,
            _ => null
        };
    }

    internal static SqlDbType? ToSqlDbType(this string? typeName)
    {
        return typeName?.ToLower() switch
        {
            "bigint" => SqlDbType.BigInt,
            "binary" => SqlDbType.Binary,
            "bit" => SqlDbType.Bit,
            "char" => SqlDbType.Char,
            "date" => SqlDbType.Date,
            "datetime" => SqlDbType.DateTime,
            "datetime2" => SqlDbType.DateTime2,
            "datetimeoffset" => SqlDbType.DateTimeOffset,
            "decimal" => SqlDbType.Decimal,
            "float" => SqlDbType.Float,
            "image" => SqlDbType.Image,
            "int" => SqlDbType.Int,
            "money" => SqlDbType.Money,
            "nchar" => SqlDbType.NChar,
            "ntext" => SqlDbType.NText,
            "numeric" => SqlDbType.Decimal,
            "nvarchar" => SqlDbType.NVarChar,
            "real" => SqlDbType.Real,
            "smalldatetime" => SqlDbType.SmallDateTime,
            "smallint" => SqlDbType.SmallInt,
            "smallmoney" => SqlDbType.SmallMoney,
            "sql_variant" => SqlDbType.Variant,
            "text" => SqlDbType.Text,
            "time" => SqlDbType.Time,
            "timestamp" => SqlDbType.Timestamp,
            "tinyint" => SqlDbType.TinyInt,
            "uniqueidentifier" => SqlDbType.UniqueIdentifier,
            "varbinary" => SqlDbType.VarBinary,
            "varchar" => SqlDbType.VarChar,
            "xml" => SqlDbType.Xml,
            _ => null
        };
    }
}
